package com.probiz.estore.theme;

import com.opensymphony.module.sitemesh.Decorator;
import com.opensymphony.module.sitemesh.mapper.DefaultDecorator;
import com.opensymphony.module.sitemesh.mapper.PathMapper;
import com.probiz.estore.core.util.ContextUtil;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.dom.*;
import org.xml.sax.SAXException;

import javax.servlet.ServletException;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

/**
 * The ConfigLoader reads a configuration XML file that contains Decorator
 * definitions (name, url, init-params) and path-mappings (pattern, name).
 * <p>
 * These can then be accessed by the getDecoratorByName() methods and
 * getMappedName() methods respectively.
 * </p>
 * <p>
 * The DTD for the configuration file in old (deprecated) format is located at
 * <a href="http://www.opensymphony.com/dtds/sitemesh_1_0_decorators.dtd">
 * http://www.opensymphony.com/dtds/sitemesh_1_0_decorators.dtd </a>.
 * </p>
 * <p>
 * The DTD for the configuration file in new format is located at <a
 * href="http://www.opensymphony.com/dtds/sitemesh_1_5_decorators.dtd">
 * http://www.opensymphony.com/dtds/sitemesh_1_5_decorators.dtd </a>.
 * </p>
 * <p>
 * Editing the config file will cause it to be auto-reloaded.
 * </p>
 * <p>
 * This class is used by ConfigDecoratorMapper, and uses PathMapper for pattern
 * matching.
 * </p>
 * <P>
 * 修改：基于com.opensymphony.module.sitemesh.mapper.ConfigLoader，加入动态模板包支持。主要修改：相对模板包的配置文件的路径；储存装饰器时相对模板包的文件的路径。
 * </P>
 * <P>
 * 说明：在TemplateSetPageFilter可以在运行时动态添加模板包路径；更灵活，但牺牲一点点性能。所以关闭在储存装饰器时处理模板包路径的处理。
 * </P>
 *
 * @author <a href="mailto:joe@truemesh.com">Joe Walnes</a>
 * @author <a href="mailto:pathos@pandora.be">Mathias Bogaert</a>
 * @version $Revision: 1.7 $
 * @see com.opensymphony.module.sitemesh.mapper.ConfigDecoratorMapper
 * @see com.opensymphony.module.sitemesh.mapper.PathMapper
 */

public class TemplateConfigLoader {
	private static final Log logger = LogFactory.getLog(TemplateConfigLoader.class);
    /** 
	 * Override default behavior of element.getAttribute (returns the empty
	 * string) to return null.
	 */
	private static String getAttribute(Element element, String name) {
		if (element != null && element.getAttribute(name) != null
				&& !element.getAttribute(name).trim().equals("")) {
			return element.getAttribute(name).trim();
		} else {
			return null;
		}
	}

	/**
	 * With a given parent XML Element, find the text contents of the child
	 * element with supplied name.
	 */
	private static String getContainedText(Node parent, String childTagName) {
		try {
			Node tag = ((Element) parent).getElementsByTagName(childTagName)
					.item(0);
			String text = ((Text) tag.getFirstChild()).getData();
			return text;
		} catch (Exception e) {
			return null;
		}
	}

	private File		configFile		= null;

	private String		configFileName	= null;

	private Map			decorators		= null;

	private PathMapper	pathMapper		= null;

	/** Create new ConfigLoader using supplied filename and config. */
	public TemplateConfigLoader() throws ServletException {
		// 修改说明：令到配置文件decorator.xml可以在模板包的目录下，如/tss/ts1/decorators/decorator.xml；忽略sitemesh.xml里面的路径(configFileName)
        try { 
            this.configFileName = TemplateUtil.getDecoratorsFileName();
            if (!ContextUtil.getInstance().isFileExists(configFileName)) {
                // 该模板包没有指定配置文件，使用缺省的
                configFileName = "/WEB-INF/classes/conf/decorators.xml";
            }
            this.configFile = new File(ContextUtil.getServletContext().getRealPath(configFileName));
            loadConfig();
        } catch (ServletException e) { 
           if(logger.isErrorEnabled()){
            logger.error(e);
           }
        }
    }

	/** Retrieve Decorator based on name specified in configuration file. */
	public Decorator getDecoratorByName(String name) throws ServletException {

		return (Decorator) decorators.get(name);
	}

	/** Get name of Decorator mapped to given path. */
	public String getMappedName(String path) throws ServletException {
		// refresh();
		return pathMapper.get(path);
	}

	/** Load configuration from file. */
	private synchronized void loadConfig() throws ServletException {
		try {
			// Build a document from the file
			DocumentBuilderFactory factory = DocumentBuilderFactory
					.newInstance();
			DocumentBuilder builder = factory.newDocumentBuilder();

			Document document = null;
			if (configFile != null && configFile.canRead()) {
				document = builder.parse(configFile);
			} else {
				document = builder.parse(ContextUtil.getServletContext()
						.getResourceAsStream(configFileName));
			}

			// Parse the configuration document
			parseConfig(document);
		} catch (ParserConfigurationException e) {
			throw new ServletException("Could not get XML parser", e);
		} catch (IOException e) {
			throw new ServletException("Could not read the config file: "
					+ configFileName, e);
		} catch (SAXException e) {
			throw new ServletException("Could not parse the config file: "
					+ configFileName, e);
		} catch (IllegalArgumentException e) {
			throw new ServletException("Could not find the config file: "
					+ configFileName, e);
		}
	}

	/** Parse configuration from XML document. */
	private synchronized void parseConfig(Document document) {
		Element root = document.getDocumentElement();

		// get the default directory for the decorators
		// 改动说明：在defaultDir增加当前使用的模板包的路径（注意，不是session里面的，所以动态切换后会影响所有用户）
		// String defaultDir = ContextUtil.getInstance().getTsPath() +
		// "/decorators";
		String defaultDir = getAttribute(root, "defaultdir");

		// Clear previous config
		pathMapper = new PathMapper();
		decorators = new HashMap();

		// Get decorators
		NodeList decoratorNodes = root.getElementsByTagName("decorator");
		Element decoratorElement = null;

		for (int i = 0; i < decoratorNodes.getLength(); i++) {
			String name = null, page = null, uriPath = null, role = null;

			// get the current decorator element
			decoratorElement = (Element) decoratorNodes.item(i);

			if (getAttribute(decoratorElement, "name") != null) {
				// The new format is used
				name = getAttribute(decoratorElement, "name");
				page = getAttribute(decoratorElement, "page");
				uriPath = getAttribute(decoratorElement, "webapp");
				role = getAttribute(decoratorElement, "role");

				// Append the defaultDir
				if (defaultDir != null && page != null && page.length() > 0
						&& !page.startsWith("/")) {
					if (page.charAt(0) == '/')
						page = defaultDir + page;
					else
						page = defaultDir + '/' + page;
				}

				// The uriPath must begin with a slash
				if (uriPath != null && uriPath.length() > 0) {
					if (uriPath.charAt(0) != '/')
						uriPath = '/' + uriPath;
				}

				// Get all <pattern>...</pattern> and
				// <url-pattern>...</url-pattern> nodes and add a mapping
				populatePathMapper(decoratorElement
						.getElementsByTagName("pattern"), role, name);
				populatePathMapper(decoratorElement
						.getElementsByTagName("url-pattern"), role, name);
			} else {
				// NOTE: Deprecated format
				name = getContainedText(decoratorNodes.item(i),
						"decorator-name");
				page = getContainedText(decoratorNodes.item(i), "resource");
				// We have this here because the use of jsp-file is deprecated,
				// but we still want
				// it to work.
				if (page == null)
					page = getContainedText(decoratorNodes.item(i), "jsp-file");
			}

			Map params = new HashMap();

			NodeList paramNodes = decoratorElement
					.getElementsByTagName("init-param");
			for (int ii = 0; ii < paramNodes.getLength(); ii++) {
				String paramName = getContainedText(paramNodes.item(ii),
						"param-name");
				String paramValue = getContainedText(paramNodes.item(ii),
						"param-value");
				params.put(paramName, paramValue);
			}
			storeDecorator(new DefaultDecorator(name, page, uriPath, role,
					params));
		}

		// Get (deprecated format) decorator-mappings
		NodeList mappingNodes = root.getElementsByTagName("decorator-mapping");
		for (int i = 0; i < mappingNodes.getLength(); i++) {
			Element n = (Element) mappingNodes.item(i);
			String name = getContainedText(mappingNodes.item(i),
					"decorator-name");

			// Get all <url-pattern>...</url-pattern> nodes and add a mapping
			populatePathMapper(n.getElementsByTagName("url-pattern"), null,
					name);
		}
	}

	/**
	 * Extracts each URL pattern and adds it to the pathMapper map.
	 */
	private void populatePathMapper(NodeList patternNodes, String role,
			String name) {
		for (int j = 0; j < patternNodes.getLength(); j++) {
			Element p = (Element) patternNodes.item(j);
			Text patternText = (Text) p.getFirstChild();
			if (patternText != null) {
				String pattern = patternText.getData().trim();
				if (pattern != null) {
					if (role != null) {
						// concatenate name and role to allow more
						// than one decorator per role
						pathMapper.put(name + role, pattern);
					} else {
						pathMapper.put(name, pattern);
					}
				}
			}
		}
	}

	/** Check if configuration file has been updated, and if so, reload. */
	public synchronized void refresh() throws ServletException {
		loadConfig();
	}

	/** Store Decorator in Map */
	private void storeDecorator(Decorator d) {
		if (d.getRole() != null) {
			decorators.put(d.getName() + d.getRole(), d);
		} else {
			decorators.put(d.getName(), d);
		}
	}
}
